﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DarksideFilter.Internal;

namespace DarksideFilter
{
	public class Location
	{
		private int _landblock;
		private double _xOffset;
		private double _yOffset;
		private double _zOffset;

		private double _longitude; // West to east - X - landblock units
		private double _latitude; // North to south - Y - landblock units

		private bool _hasPositionChanged;
		private bool _isIndoor;

		private bool _mustUpdateCoordinates;
		private string _coordinates;
		private int _coordinatesDigit;
		private string _coordinatesSeparator;

		private Dungeon _dungeon;

		public Location()
		{
			_landblock = 0;
			_xOffset = 0;
			_yOffset = 0;
			_zOffset = 0;

			_hasPositionChanged = false;
			_isIndoor = false;

			_dungeon = new Dungeon(0, "");

			SetCoordinatesFormat(1, ", ");
			UpdatePosition();
		}

		#region Private Methods

		private bool IsContiguousDungeon(Dungeon dungeon)
		{
			switch (dungeon.ID)
			{
				case 0xF784: // Tusker Emporium
					return true;

				case 0x934B: // Xarabydun
					return true;

				case 0xC75E: // Greenmire B&B
					return true;

				case 0xBB62: // Swap Temple anteroom
					return true;

				case 0xB131: // Dungeon of Tatters anteroom
					return true;

				case 0x9626: // Lugian Dwelling (North of Qalaba'r)
					return true;

				default:
					return false;
			}
		}

		private bool IsSubSea(int landblockX, int landblockY)
		{
			// Most dungeons are under the extreme western ocean
			if (landblockX < 4)
			{
				return true;
			}

			// Some Residential Quarters and a few other dungeons are in the inland sea
			if (landblockX >= 0x50 && landblockX < 0x70 && landblockY >= 0x40 && landblockY < 0x80)
			{
				return true;
			}

			// Some Residential Quarters are under the extreme southern ocean
			if (landblockY == 0)
			{
				return true;
			}

			return false;
		}

		private void OnPositionChange()
		{
			_hasPositionChanged = true;
			_mustUpdateCoordinates = true;
		}

		private void UpdatePosition()
		{
			bool useGlobalCoordinates;
			int landblockX;
			int landblockY;
			Dungeon oldDungeon;

			/* Landblock = 32 bits dword
				[ F F  |  F F  |  F F  |  F F ]
				   ^       ^       ^       ^
				   1       2       3       4
    
			A landblock is     192 x 192 units - Landblock origin (0,0) at SW corner (bottom left)
			                   0.8 x 0.8 map units (as displayed in coordinates)
			A map unit is therefore 240 x 240 units
			The landblock at 0.0N 0.0W is [7F|7F|00|1B] and its center is (84,84) supposedly (empirical) */

			// Get landblock
			landblockX = (_landblock / 0x1000000) & 0xFF; // Byte 1
			landblockY = (_landblock / 0x10000) & 0xFF; // Byte 2

			oldDungeon = _dungeon;

			_isIndoor = (_landblock & 0xFF00) != 0;

			if (_isIndoor)
			{
				// Assume we are in a dungeon
				_dungeon.ID = (_landblock / 0x10000) & 0xFFFF; // Byte 1 and 2

				if (IsSubSea(landblockX, landblockY))
				{
					useGlobalCoordinates = false;
				}
				else if (IsContiguousDungeon(_dungeon))
				{
					useGlobalCoordinates = true;
				}
				else
				{
					useGlobalCoordinates = true;
					_dungeon.ID = 0;
				}

				if (oldDungeon.ID == 0 && _dungeon.ID != 0)
				{
					// TODO: Find dungeon
				}
			}
			else
			{
				useGlobalCoordinates = true;
				_dungeon.ID = 0;
			}

			if (useGlobalCoordinates)
			{
				_longitude = (landblockX - 0x7F) * 192 + _xOffset - 84;
				_latitude = (landblockY - 0x7F) * 192 + _yOffset - 84;
			}
			else
			{
				_longitude = _xOffset - 84;
				_latitude = _yOffset - 84;
			}

			_hasPositionChanged = false;
		}

		private void UpdateCoordinates()
		{
			string format = "0." + _coordinatesDigit.ToString("0");
			double longitude = _longitude / 240;
			double latitude = _latitude / 240;

			if (_latitude >= 0)
			{
				_coordinates = string.Format(format, latitude) + "N";
			}
			else
			{
				_coordinates = string.Format(format, -latitude) + "S";
			}

			_coordinates += _coordinatesSeparator;

			if (_longitude >= 0)
			{
				_coordinates += string.Format(format, longitude) + "E";
			}
			else
			{
				_coordinates += string.Format(format, -longitude) + "W";
			}
		}

		#endregion

		#region Public Methods

		public Location Clone()
		{
			Location newLocation = new Location();
			newLocation.Landblock = _landblock;
			newLocation.XOffset = _xOffset;
			newLocation.YOffset = _yOffset;
			newLocation.ZOffset = _zOffset;

			return newLocation;
		}

		public double DistanceTo(Location location)
		{
			return Math.Sqrt(SquareDistanceTo(location));
		}

		public bool Equals(Location location)
		{
			return _landblock == location.Landblock && _xOffset == location.XOffset && _yOffset == location.YOffset && _zOffset == location.ZOffset;
		}

		public void ForcePositionUpdate()
		{
			UpdatePosition();
		}

		public void SetCoordinatesFormat(int digit, string separator)
		{
			_coordinatesDigit = digit;
			_coordinatesSeparator = separator;
			_mustUpdateCoordinates = true;
		}

		public double SquareDistanceTo(Location location)
		{
			return Utility.GetSquareRange(_longitude, _latitude, _zOffset, location.Longitude, location.Latitude, location.ZOffset);
		}

		#endregion

		#region Public Properties

		public string Coordinates
		{
			get
			{
				if (_hasPositionChanged)
				{
					UpdatePosition();
				}

				if (_mustUpdateCoordinates)
				{
					UpdateCoordinates();
				}

				return _coordinates;
			}
		}

		public Dungeon Dungeon
		{
			get
			{
				return _dungeon;
			}
		}

		public bool IsIndoor
		{
			get
			{
				if (_hasPositionChanged)
				{
					UpdatePosition();
				}

				return _isIndoor;
			}
		}

		public bool IsInDungeon
		{
			get
			{
				if (_hasPositionChanged)
				{
					UpdatePosition();
				}

				return _isIndoor && _dungeon != null;
			}
		}

		public int Landblock
		{
			get
			{
				return _landblock;
			}

			set
			{
				if (value != _landblock)
				{
					OnPositionChange();
				}

				_landblock = value;
			}
		}

		public double Latitude
		{
			get
			{
				if (_hasPositionChanged)
				{
					UpdatePosition();
				}

				return _latitude;
			}
		}

		public double Longitude
		{
			get
			{
				if (_hasPositionChanged)
				{
					UpdatePosition();
				}

				return _longitude;
			}
		}

		public double XOffset
		{
			get
			{
				return _xOffset;
			}

			set
			{
				if (value != _xOffset)
				{
					OnPositionChange();
				}

				_xOffset = value;
			}
		}

		public double YOffset
		{
			get
			{
				return _yOffset;
			}

			set
			{
				if (value != _yOffset)
				{
					OnPositionChange();
				}

				_yOffset = value;
			}
		}

		public double ZOffset
		{
			get
			{
				return _zOffset;
			}

			set
			{
				if (value != _zOffset)
				{
					OnPositionChange();
				}

				_zOffset = value;
			}
		}

		#endregion
	}
}